package com.yifeng.repo.base.security.context.listener;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.yifeng.repo.base.constant.BaseConstant;
import com.yifeng.repo.base.security.auth.constant.SecurityConstant;
import com.yifeng.repo.base.security.auth.constant.SecurityTypeEnum;
import com.yifeng.repo.base.security.context.manager.RequestBodyManager;
import com.yifeng.repo.base.security.context.manager.RequestContextManager;
import com.yifeng.repo.base.security.context.manager.RequestHeaderManager;
import com.yifeng.repo.base.utils.common.BaseUtil;
import com.yifeng.repo.base.utils.servlet.RequestHelper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.util.PatternMatchUtils;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;

import static com.yifeng.repo.base.security.context.manager.RequestHeaderManager.*;

/**
 * Created by daibing on 2023/3/27.
 */
@Slf4j
public class AppContextListener implements ServletRequestListener {
    private final List<String> copyHeaderPrefixList;
    private final String[] propertyTransformerUriPatterns;
    private boolean enablePrintRequestCostTime;

    public AppContextListener() {
        this.copyHeaderPrefixList = Lists.newArrayList("x-");
        this.propertyTransformerUriPatterns = new String[0];
    }

    public AppContextListener(List<String> copyHeaderPrefixList) {
        this.copyHeaderPrefixList = copyHeaderPrefixList;
        this.propertyTransformerUriPatterns = new String[0];
    }

    public AppContextListener(List<String> copyHeaderPrefixList, List<String> propertyTransformerUriPatternList, boolean enablePrintRequestCostTime) {
        this.copyHeaderPrefixList = copyHeaderPrefixList;
        this.propertyTransformerUriPatterns = Optional.ofNullable(propertyTransformerUriPatternList)
                .orElse(Collections.emptyList())
                .toArray(new String[0]);
        this.enablePrintRequestCostTime = enablePrintRequestCostTime;
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        // 增加请求开始时间
        RequestContextManager.setRequestStartTime(System.currentTimeMillis());

        // 拷贝header
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        RequestHeaderManager.set(REQ_METHOD, request.getMethod());
        RequestHeaderManager.set(REQ_URL, request.getRequestURL().toString());
        RequestHeaderManager.set(REQ_URI, request.getRequestURI());
        RequestHeaderManager.set(REQ_CONTENT_TYPE, request.getContentType());
        RequestHeaderManager.set(REQ_IP, RequestHelper.getRemoteAddress(request));
        RequestHeaderManager.set(REQ_HOST, RequestHelper.getRemoteHost(request));
        RequestHeaderManager.set(BaseConstant.TRACE_ID, BaseUtil.getSkywalkingTraceId());
        this.copyBizRequestHeader(request, copyHeaderPrefixList);

        // 将traceId和frontendId写入MDC支持业务使用，尤其是frontendId支持日志模块获取写入日志中心
        if (!Strings.isNullOrEmpty(RequestHeaderManager.get(BaseConstant.TRACE_ID))) {
            MDC.put(BaseConstant.TRACE_ID, RequestHeaderManager.get(BaseConstant.TRACE_ID));
        }
        if (!Strings.isNullOrEmpty(RequestHeaderManager.get(BaseConstant.FRONTEND_ID))) {
            MDC.put(BaseConstant.FRONTEND_ID, RequestHeaderManager.get(BaseConstant.FRONTEND_ID));
        }

        // 确保退出的时候对象已经加载
        RequestBodyManager.set(null);
        RequestContextManager.setReqAppId(request.getHeader(SecurityConstant.REQ_APP_ID));
        RequestContextManager.setSecurityType(SecurityTypeEnum.resolve(request.getHeader(SecurityConstant.SECURITY_TYPE)));
        RequestContextManager.setTransferRespDataKey(PatternMatchUtils.simpleMatch(propertyTransformerUriPatterns, request.getRequestURI()));

        // 尝试解析门店参数，填入请求上下文。兼容老代码仅Get请求读取请求参数。
        // 当调用方是Post请求，Content-Type为application/x-www-form-urlencoded，getParameter/getInputStream/getReader三个方法互斥，只能调其中一个。
        // 当调用方是Post请求，Content-Type为其他类型（application/json），getParameter()获取不到内容，也不会影响后续getInputStream/getReader获取内容。
        if (BaseConstant.HTTP_GET_METHOD.equalsIgnoreCase(request.getMethod())) {
            RequestContextManager.setShardingStoreCode(request.getParameter(BaseConstant.STORE_CODE_KEY));
        }
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        if (enablePrintRequestCostTime) {
            log.info("ReqAppId={}, URI={}, 请求耗时={}ms",
                    RequestContextManager.getReqAppId(), RequestHeaderManager.get(REQ_URI),
                    System.currentTimeMillis() - RequestContextManager.getRequestStartTime());
        }
        MDC.clear();
        RequestBodyManager.clear();
        RequestContextManager.clear();
        RequestHeaderManager.clear();
    }

    protected void copyBizRequestHeader(HttpServletRequest request, List<String> copyHeaderPrefixList) {
        Enumeration<String> hns = request.getHeaderNames();
        if (hns == null || !hns.hasMoreElements()) {
            return;
        }

        while (hns.hasMoreElements()) {
            String name = hns.nextElement();
            if (Strings.isNullOrEmpty(name)) {
                continue;
            }

            String head = name.trim().toLowerCase();
            if (!this.matchHeaderPrefix(head, copyHeaderPrefixList)) {
                continue;
            }

            String value = request.getHeader(name);
            if (Strings.isNullOrEmpty(value)) {
                continue;
            }

            RequestHeaderManager.set(name.trim(), value.trim());
        }
    }

    private boolean matchHeaderPrefix(String head, List<String> copyHeaderPrefixList) {
        for (String headerPrefix : copyHeaderPrefixList) {
            if (head.startsWith(headerPrefix)) {
                return true;
            }
        }
        return false;
    }

}
